import Enumerable, { IGrouping } from 'linq';
import groupbyonly from './_groupby-only';
import groupbysimple from './_groupby-simple';

type alculateField<T> = [T, 'sum' | 'average' | 'max' | 'min', string?];

/**
 * 分组计算
 * @param data JSON数组
 * @param group_fields 分组的字段
 * @param calculate_fields 要参加计算的原子操作,格式为[字段名，计算方法，字段别名]
 */
export default function groupby<T = {}>(data: T[], group_fields: Array<keyof T>, calculate_fields: Array<alculateField<keyof T>>) {
	if (calculate_fields.length === 0) {
		return groupbyonly(data, group_fields);
	} else if (calculate_fields.length === 1) {
		const [field, method, alias] = calculate_fields[0];
		return groupbysimple(data, group_fields, field, method, alias);
	} else {
		if (group_fields.length === 0) {
			throw new Error('Group Field needed!');
		} else if (group_fields.length === 1) {
			const group_field = group_fields[0];
			return Enumerable.from(data).groupBy((it) => {
				return it[group_field];
			}, (it) => {
				return it;
			}, (it, group: IGrouping<string, T>) => {
				const obj = {
					[group_field]: it
				} as Partial<T> & { [field: string]: any; };
				const values = Enumerable.from(group.getSource());
				return calculate_fields.reduce((pre, [field, method, alias]) => {
					return {
						...pre,
						[alias || field]: values[method]((val) => {
							return val[field] as unknown as number;
						})
					};
				}, obj);
			}).toArray();
		} else {
			return Enumerable.from(data).groupBy((it) => {
				return JSON.stringify(group_fields.reduce((pre, cur) => {
					return {
						...pre,
						[cur]: it[cur]
					};
				}, {}));
			}, (it) => {
				return it;
			}, (it, group: IGrouping<string, T>) => {
				const item = JSON.parse(it);
				const obj = group_fields.reduce((pre, cur) => {
					return {
						...pre,
						[cur]: item[cur]
					};
				}, {} as Partial<T> & { [field: string]: any; });
				const values = Enumerable.from(group.getSource());
				return calculate_fields.reduce((pre, [field, method, alias]) => {
					return {
						...pre,
						[alias || field]: values[method]((val) => {
							return val[field] as unknown as number;
						})
					};
				}, obj);
			}).toArray();
		}
	}
}
